package com.che.libcommon.api;

import android.support.v4.util.Pair;
import android.text.TextUtils;
import android.util.Log;
import com.blankj.utilcode.util.CacheDiskUtils;
import com.che.common.libcommon.BuildConfig;
import com.che.libcommon.utils.JsonKit;
import com.che.libcommon.utils.RxHelper;
import com.che.libcommon.utils.optional.Optional;

import com.google.gson.JsonSyntaxException;
import com.google.gson.reflect.TypeToken;
import io.reactivex.Flowable;
import io.reactivex.Single;
import io.reactivex.functions.Consumer;
import io.reactivex.functions.Function;
import io.reactivex.functions.Predicate;
import io.reactivex.observers.ResourceSingleObserver;
import io.reactivex.schedulers.Schedulers;
import java.lang.reflect.Type;
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import okhttp3.OkHttpClient.Builder;
import okhttp3.logging.HttpLoggingInterceptor.Level;
import retrofit2.Retrofit;
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
import retrofit2.converter.gson.GsonConverterFactory;

public class BaseApiClient {

    protected static <T> Function<BaseResult<T>, Optional<T>> dataMap() {
        return new Function<BaseResult<T>, Optional<T>>() {
            @Override
            public Optional<T> apply(BaseResult<T> ret) throws Exception {
                if (ret != null) {
                    if (ret.isSuccess()) {
                        return Optional.ofNullable(ret.data);
                    } else {
                        throw new ApiException(ret.msg,ret);
                    }
                }
                throw new ApiException("response is null !",(BaseResult) null);
            }
        };
    }


    protected <T> Single<Optional<T>> wrapToSingle(Flowable<? extends BaseResult<T>> flowable) {
        return wrapToSingle(flowable,true);
    }

    protected <T> Single<Optional<T>> wrapToSingle(Flowable<? extends BaseResult<T>> flowable,
        final String cacheKey,final Type type,final long cacheTime) {
        Single<Optional<T>> fromNet = wrapToSingle(flowable, true).doOnSuccess(
            new Consumer<Optional<T>>() {
                @Override
                public void accept(Optional<T> tOptional) throws Exception {
                    Log.e("test", "net success accept --> "+tOptional.orNull());
                    saveCache(cacheKey,tOptional.orNull(),cacheTime);
                }
            });
        Single<Optional<T>> fromCache = getFromCache(cacheKey,type);
        return Single.concat(fromCache, fromNet).filter(new Predicate<Optional<T>>() {
            @Override
            public boolean test(Optional<T> tOptional) throws Exception {
                return tOptional.isPresent();
            }
        }).first(Optional.<T>absent()).doOnError(new Consumer<Throwable>() {
            @Override
            public void accept(Throwable throwable) throws Exception {
                //Log.e("test", "error accept --> ",throwable);
            }
        }).doOnSuccess(new Consumer<Optional<T>>() {
            @Override
            public void accept(Optional<T> tOptional) throws Exception {
                Log.e("test", "final success accept --> "+tOptional.orNull());
            }
        });
        //return wrapToSingle(flowable,true);
    }

    protected  <T> Single<Optional<T>> wrapToSingle(Flowable<? extends BaseResult<T>> flowable,boolean switch2Main) {
        if(switch2Main){
            return flowable
                .compose(RxHelper.<BaseResult<T>>io2main())
                .map(BaseApiClient.<T>dataMap())
                .singleOrError();
        }else {
            return flowable
                .compose(RxHelper.<BaseResult<T>>switch2io())
                .map(BaseApiClient.<T>dataMap())
                .singleOrError();
        }

    }


    protected OkHttpClient.Builder createHttpClientBuilder() {
        return new Builder();
    }


    protected Retrofit.Builder createRetrofitBuilder(OkHttpClient client) {
        return new Retrofit.Builder().baseUrl(getBaseUrl())
            .addConverterFactory(GsonConverterFactory.create(JsonKit.GSON))
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .client(client);
    }

    protected String getBaseUrl() {
        return null;
    }


    protected Retrofit createRetrofit() {
        Builder builder = createHttpClientBuilder();

        if(BuildConfig.DEBUG){
            builder =  builder.addInterceptor(new okhttp3.logging.HttpLoggingInterceptor().setLevel(Level.BASIC));
        }

        OkHttpClient okHttpClient = builder.build();

        return createRetrofitBuilder(okHttpClient).build();
    }

    protected Retrofit createRetrofit(Interceptor... interceptors) {

        final Builder builder = createHttpClientBuilder();
        if (interceptors != null) {
            for (Interceptor interceptor : interceptors) {
                builder.addInterceptor(interceptor);
            }
        }

        OkHttpClient okHttpClient = builder.build();

        return createRetrofitBuilder(okHttpClient).build();
    }

    public BaseApiClient() {
        cacheUtils = CacheDiskUtils.getInstance("api_cache");
    }

    protected CacheDiskUtils cacheUtils;

    protected <T> Single<Optional<T>> getFromCache(String key,final Type type){
        return Single.just(key).map(new Function<String, Optional<T>>() {
            @Override
            public Optional<T> apply(String s) throws Exception {
                //Log.e("test", "getFromCache apply --> "+Thread.currentThread()+"   "+type);
                String string = cacheUtils.getString(s);
                if(!TextUtils.isEmpty(string)){
                    try {
                        T data = JsonKit.GSON.fromJson(string, type);
                        return Optional.ofNullable(data);
                    } catch (Exception e) {
                        cacheUtils.remove(s);
                        e.printStackTrace();
                    }
                }
                return Optional.absent();
            }
        }).compose(RxHelper.<Optional<T>>io2main());
    }

    protected <T> void saveCache(String cacheKey,T data,final long cacheTime){
        if(TextUtils.isEmpty(cacheKey) || data == null){
            return;
        }
        Single.just(Pair.create(cacheKey,data))
            .subscribeOn(Schedulers.io())
            .subscribe(new ResourceSingleObserver<Pair<String,T>>() {
            @Override
            public void onSuccess(Pair<String, T> stringTPair) {
                if (stringTPair != null && stringTPair.first != null){
                    cacheUtils.put(stringTPair.first,JsonKit.GSON.toJson(stringTPair.second),
                        (int) cacheTime);
                }
            }

            @Override
            public void onError(Throwable e) {

            }
        });
    }

    public final boolean removeCache(String key){
        return cacheUtils.remove(key);
    }

    public final boolean removeCache(){
        return cacheUtils.clear();
    }
}
